package com.sr.ibase;

import com.sr.vo.PageVo;
import org.apache.ibatis.annotations.Mapper;
import tk.mybatis.mapper.entity.Example;

import java.beans.Introspector;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;

/**
 * @author xiaochi
 * @date   2021/11/21 13:38
 * @desc   增删改查通用service
 * 这里加 @Mapper 作用：
 *  使用的tk的开源项目进行mybatis集成，百度了很多解决方案，最终看到一位前辈介绍：
 *  doScan()会扫描启动类同级目录下的mapper接口，但是合理的目录结果绝对不允许所有的mapper都在启动类目录下，所以在启动类目录下添加了一个伪mapper
*/
@Mapper
public interface IBaseService<T> {

    /**
     * 根据主键查询
     * @param id 主键
     * @return 实体对象
     */
    T findOne(Serializable id);

    /**
     * 根据实体查询
     * @param t 实体
     * @return 实体对象
     */
    T findOne(T t);

    /**
     * 根据example条件查询
     * @param example 查询条件
     * @return 实体对象
     */
    T findOne(Example example);

    /**
     * 根据criteria条件查询
     * @param criteria 查询条件
     * @return 实体对象
     */
    T findOne(Criteria<T> criteria);

    /**
     * 查询全部
     * @return 实体对象集合
     */
    List<T> findAll();

    /**
     * 根据条件查询列表
     * @param t 查询条件对象
     * @return List<T>
     */
    List<T> findByWhere(T t);

    /**
     * 根据复杂条件查询列表
     * @param example 查询条件对象
     * @return List<T>
     */
    List<T> findByWhere(Example example);

    /**
     * 分页查询列表
     * @param page 页号
     * @param size 页大小
     * @return 分页实体对象
     */
    PageVo<T> findPage(Integer page, Integer size);

    /**
     * 根据条件分页查询列表
     * @param page 页号
     * @param size 页大小
     * @param t    查询条件对象
     * @return 分页实体对象
     */
    PageVo<T> findPage(Integer page, Integer size, T t);

    /**
     * 根据复杂条件分页查询列表
     * @param page 页号
     * @param size 页大小
     * @param example 查询条件对象
     * @return 分页实体对象
     */
    PageVo<T> findPage(Integer page, Integer size, Example example);

    /**
     * 创建查询条件 CreateCriteria
     * @param clazz 实体类
     * @return Criteria<T>
     */
    Criteria<T> createCriteria(Class<T> clazz);

    /**
     * 根据Criteria（支持lambda表达式）条件查询列表
     * @param criteria （支持lambda表达式）
     * @return List<T>
     */
    List<T> findByWhere(Criteria<T> criteria);

    /**
     * 根据Criteria（支持lambda表达式）条件分页查询列表
     * @param page 当前页
     * @param size 每页数量
     * @param criteria 支持lambda表达式
     * @return PageVo<T>
     */
    PageVo<T> findPage(Integer page, Integer size, Criteria<T> criteria);

    /**
     * 新增
     * @param t 实体对象
     */
    int insert(T t);

    /**
     * 批量新增
     * @param list 实体对象
     */
    int insertBatch(List<T> list);

    /**
     * 批量新增（大数据批量新增）
     * @param list 实体对象
     */
    int insertBigBatch(List<T> list);

    /**
     * 根据主键更新
     * @param t 实体对象
     */
    int updateById(T t);

    /**
     * 根据实体对象更新
     * @param t 实体对象
     * @param example example
     */
    int update(T t, Example example);

    /**
     * 根据实体对象更新
     * @param t 实体对象
     * @param criteria 支持lambda表达式
     */
    int update(T t, Criteria<T> criteria);

    /**
     * 实体删除
     * @param t 实体对象
     * @return int
     */
    int deleteByWhere(T t);

    /**
     * 条件删除
     * @param example example
     * @return int
     */
    int deleteByWhere(Example example);

    /**
     * 条件删除 criteria
     * @param criteria 支持lambda表达式
     * @return int
     */
    int deleteByWhere(Criteria<T> criteria);

    /**
     * 主键删除
     * @param id 主键
     */
    int deleteById(Serializable id);

    /**
     * 批量删除
     * @param ids 主键集合
     */
    void deleteByIds(Serializable[] ids);

    /**
     * count 统计
     * @param t 实体
     * @return int
     */
    int count(T t);

    /**
     * count 统计
     * @param example example
     * @return int
     */
    int count(Example example);

    /**
     * count 统计
     * @param criteria 支持lambda表达式
     * @return int
     */
    int count(Criteria<T> criteria);

    /**
     * @author xiaochi
     * @date 2021/12/17 16:56
     * @desc CreateCriteria查询条件(支持lambda表达式)
     */
    class Criteria<T>{
        private Class<T> clazz;
        private Map<String,Object> params = new HashMap<>();
        private String orderBy;
        private String order;
        private boolean isAnd;
        private boolean isOr;
        private List<Criteria<T>> criteriaAndList = new ArrayList<>();
        private List<Criteria<T>> criteriaOrList = new ArrayList<>();

        public Criteria() {}

        public Criteria(Class<T> clazz) {
            this.clazz = clazz;
        }

        public Criteria<T> andEqualTo(String property, Object val){
            this.params.put(property+"_andEqualTo",val);
            return this;
        }
        public Criteria<T> andEqualTo(Fn<T,Object> fn, Object val){
            this.params.put(this.fnToFieldName(fn)+"_andEqualTo",val);
            return this;
        }
        public Criteria<T> andEqualTo(String property, boolean b, Object val){
            if (b){
                this.params.put(property+"_andEqualTo",val);
            }
            return this;
        }
        public Criteria<T> andEqualTo(Fn<T,Object> fn, boolean b, Object val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_andEqualTo",val);
            }
            return this;
        }
        public Criteria<T> andNotEqualTo(String property, Object val){
            this.params.put(property+"_andNotEqualTo",val);
            return this;
        }
        public Criteria<T> andNotEqualTo(Fn<T,Object> fn, Object val){
            this.params.put(this.fnToFieldName(fn)+"_andNotEqualTo",val);
            return this;
        }
        public Criteria<T> andNotEqualTo(String property, boolean b, Object val){
            if (b){
                this.params.put(property+"_andNotEqualTo",val);
            }
            return this;
        }
        public Criteria<T> andNotEqualTo(Fn<T,Object> fn, boolean b, Object val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_andNotEqualTo",val);
            }
            return this;
        }
        public Criteria<T> orEqualTo(String property, Object val){
            this.params.put(property+"_orEqualTo",val);
            return this;
        }
        public Criteria<T> orEqualTo(Fn<T,Object> fn, Object val){
            this.params.put(this.fnToFieldName(fn)+"_orEqualTo",val);
            return this;
        }
        public Criteria<T> orEqualTo(String property, boolean b, Object val){
            if (b){
                this.params.put(property+"_orEqualTo",val);
            }
            return this;
        }
        public Criteria<T> orEqualTo(Fn<T,Object> fn, boolean b, Object val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_orEqualTo",val);
            }
            return this;
        }
        public Criteria<T> orNotEqualTo(String property, Object val){
            this.params.put(property+"_orNotEqualTo",val);
            return this;
        }
        public Criteria<T> orNotEqualTo(Fn<T,Object> fn, Object val){
            this.params.put(this.fnToFieldName(fn)+"_orNotEqualTo",val);
            return this;
        }
        public Criteria<T> orNotEqualTo(String property, boolean b, Object val){
            if (b){
                this.params.put(property+"_orNotEqualTo",val);
            }
            return this;
        }
        public Criteria<T> orNotEqualTo(Fn<T,Object> fn, boolean b, Object val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_orNotEqualTo",val);
            }
            return this;
        }
        public Criteria<T> andLike(String property, String val){
            this.params.put(property+"_andLike",val);
            return this;
        }
        public Criteria<T> andLike(Fn<T,Object> fn, String val){
            this.params.put(this.fnToFieldName(fn)+"_andLike",val);
            return this;
        }
        public Criteria<T> andLike(String property, boolean b, String val){
            if (b){
                this.params.put(property+"_andLike",val);
            }
            return this;
        }
        public Criteria<T> andLike(Fn<T,Object> fn, boolean b, String val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_andLike",val);
            }
            return this;
        }
        public Criteria<T> andNotLike(String property, String val){
            this.params.put(property+"_andNotLike",val);
            return this;
        }
        public Criteria<T> andNotLike(Fn<T,Object> fn, String val){
            this.params.put(this.fnToFieldName(fn)+"_andNotLike",val);
            return this;
        }
        public Criteria<T> andNotLike(String property, boolean b, String val){
            if (b){
                this.params.put(property+"_andNotLike",val);
            }
            return this;
        }
        public Criteria<T> andNotLike(Fn<T,Object> fn, boolean b, String val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_andNotLike",val);
            }
            return this;
        }
        public Criteria<T> orLike(String property, String val){
            this.params.put(property+"_orLike",val);
            return this;
        }
        public Criteria<T> orLike(Fn<T,Object> fn, String val){
            this.params.put(this.fnToFieldName(fn)+"_orLike",val);
            return this;
        }
        public Criteria<T> orLike(String property, boolean b, String val){
            if (b){
                this.params.put(property+"_orLike",val);
            }
            return this;
        }
        public Criteria<T> orLike(Fn<T,Object> fn, boolean b, String val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_orLike",val);
            }
            return this;
        }
        public Criteria<T> orNotLike(String property, String val){
            this.params.put(property+"_orNotLike",val);
            return this;
        }
        public Criteria<T> orNotLike(Fn<T,Object> fn, String val){
            this.params.put(this.fnToFieldName(fn)+"_orNotLike",val);
            return this;
        }
        public Criteria<T> orNotLike(String property, boolean b, String val){
            if (b){
                this.params.put(property+"_orNotLike",val);
            }
            return this;
        }
        public Criteria<T> orNotLike(Fn<T,Object> fn, boolean b, String val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_orNotLike",val);
            }
            return this;
        }
        public Criteria<T> andGreaterThan(String property, Object val){
            this.params.put(property+"_andGreaterThan",val);
            return this;
        }
        public Criteria<T> andGreaterThan(Fn<T,Object> fn, Object val){
            this.params.put(this.fnToFieldName(fn)+"_andGreaterThan",val);
            return this;
        }
        public Criteria<T> andGreaterThan(String property, boolean b, Object val){
            if (b){
                this.params.put(property+"_andGreaterThan",val);
            }
            return this;
        }
        public Criteria<T> andGreaterThan(Fn<T,Object> fn, boolean b, Object val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_andGreaterThan",val);
            }
            return this;
        }
        public Criteria<T> orGreaterThan(String property, Object val){
            this.params.put(property+"_orGreaterThan",val);
            return this;
        }
        public Criteria<T> orGreaterThan(Fn<T,Object> fn, Object val){
            this.params.put(this.fnToFieldName(fn)+"_orGreaterThan",val);
            return this;
        }
        public Criteria<T> orGreaterThan(String property, boolean b, Object val){
            if (b){
                this.params.put(property+"_orGreaterThan",val);
            }
            return this;
        }
        public Criteria<T> orGreaterThan(Fn<T,Object> fn, boolean b, Object val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_orGreaterThan",val);
            }
            return this;
        }
        public Criteria<T> andGreaterThanOrEqualTo(String property, Object val){
            this.params.put(property+"_andGreaterThanOrEqualTo",val);
            return this;
        }
        public Criteria<T> andGreaterThanOrEqualTo(Fn<T,Object> fn, Object val){
            this.params.put(this.fnToFieldName(fn)+"_andGreaterThanOrEqualTo",val);
            return this;
        }
        public Criteria<T> andGreaterThanOrEqualTo(String property, boolean b, Object val){
            if (b){
                this.params.put(property+"_andGreaterThanOrEqualTo",val);
            }
            return this;
        }
        public Criteria<T> andGreaterThanOrEqualTo(Fn<T,Object> fn, boolean b, Object val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_andGreaterThanOrEqualTo",val);
            }
            return this;
        }
        public Criteria<T> orGreaterThanOrEqualTo(String property, Object val){
            this.params.put(property+"_orGreaterThanOrEqualTo",val);
            return this;
        }
        public Criteria<T> orGreaterThanOrEqualTo(Fn<T,Object> fn, Object val){
            this.params.put(this.fnToFieldName(fn)+"_orGreaterThanOrEqualTo",val);
            return this;
        }
        public Criteria<T> orGreaterThanOrEqualTo(String property, boolean b, Object val){
            if (b){
                this.params.put(property+"_orGreaterThanOrEqualTo",val);
            }
            return this;
        }
        public Criteria<T> orGreaterThanOrEqualTo(Fn<T,Object> fn, boolean b, Object val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_orGreaterThanOrEqualTo",val);
            }
            return this;
        }
        public Criteria<T> andLessThan(String property, Object val){
            this.params.put(property+"_andLessThan",val);
            return this;
        }
        public Criteria<T> andLessThan(Fn<T,Object> fn, Object val){
            this.params.put(this.fnToFieldName(fn)+"_andLessThan",val);
            return this;
        }
        public Criteria<T> andLessThan(String property, boolean b, Object val){
            if (b){
                this.params.put(property+"_andLessThan",val);
            }
            return this;
        }
        public Criteria<T> andLessThan(Fn<T,Object> fn, boolean b, Object val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_andLessThan",val);
            }
            return this;
        }
        public Criteria<T> orLessThan(String property, Object val){
            this.params.put(property+"_orLessThan",val);
            return this;
        }
        public Criteria<T> orLessThan(Fn<T,Object> fn, Object val){
            this.params.put(this.fnToFieldName(fn)+"_orLessThan",val);
            return this;
        }
        public Criteria<T> orLessThan(String property, boolean b, Object val){
            if (b){
                this.params.put(property+"_orLessThan",val);
            }
            return this;
        }
        public Criteria<T> orLessThan(Fn<T,Object> fn, boolean b, Object val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_orLessThan",val);
            }
            return this;
        }
        public Criteria<T> andLessThanOrEqualTo(String property, Object val){
            this.params.put(property+"_andLessThanOrEqualTo",val);
            return this;
        }
        public Criteria<T> andLessThanOrEqualTo(Fn<T,Object> fn, Object val){
            this.params.put(this.fnToFieldName(fn)+"_andLessThanOrEqualTo",val);
            return this;
        }
        public Criteria<T> andLessThanOrEqualTo(String property, boolean b, Object val){
            if (b){
                this.params.put(property+"_andLessThanOrEqualTo",val);
            }
            return this;
        }
        public Criteria<T> andLessThanOrEqualTo(Fn<T,Object> fn, boolean b, Object val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_andLessThanOrEqualTo",val);
            }
            return this;
        }
        public Criteria<T> orLessThanOrEqualTo(String property, Object val){
            this.params.put(property+"_orLessThanOrEqualTo",val);
            return this;
        }
        public Criteria<T> orLessThanOrEqualTo(Fn<T,Object> fn, Object val){
            this.params.put(this.fnToFieldName(fn)+"_orLessThanOrEqualTo",val);
            return this;
        }
        public Criteria<T> orLessThanOrEqualTo(String property, boolean b, Object val){
            if (b){
                this.params.put(property+"_orLessThanOrEqualTo",val);
            }
            return this;
        }
        public Criteria<T> orLessThanOrEqualTo(Fn<T,Object> fn, boolean b, Object val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_orLessThanOrEqualTo",val);
            }
            return this;
        }
        public Criteria<T> andIn(String property, List<String> val){
            this.params.put(property+"_andIn",String.join(",",val));
            return this;
        }
        public Criteria<T> andIn(Fn<T,Object> fn, List<String> val){
            this.params.put(this.fnToFieldName(fn)+"_andIn",String.join(",",val));
            return this;
        }
        public Criteria<T> andIn(String property, boolean b, List<String> val){
            if (b){
                this.params.put(property+"_andIn",String.join(",",val));
            }
            return this;
        }
        public Criteria<T> andIn(Fn<T,Object> fn, boolean b, List<String> val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_andIn",String.join(",",val));
            }
            return this;
        }
        public Criteria<T> andNotIn(String property, List<String> val){
            this.params.put(property+"_andNotIn",String.join(",",val));
            return this;
        }
        public Criteria<T> andNotIn(Fn<T,Object> fn, List<String> val){
            this.params.put(this.fnToFieldName(fn)+"_andNotIn",String.join(",",val));
            return this;
        }
        public Criteria<T> andNotIn(String property, boolean b, List<String> val){
            if (b){
                this.params.put(property+"_andNotIn",String.join(",",val));
            }
            return this;
        }
        public Criteria<T> andNotIn(Fn<T,Object> fn, boolean b, List<String> val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_andNotIn",String.join(",",val));
            }
            return this;
        }
        public Criteria<T> orIn(String property, List<String> val){
            this.params.put(property+"_orIn",String.join(",",val));
            return this;
        }
        public Criteria<T> orIn(Fn<T,Object> fn, List<String> val){
            this.params.put(this.fnToFieldName(fn)+"_orIn",String.join(",",val));
            return this;
        }
        public Criteria<T> orIn(String property, boolean b, List<String> val){
            if (b){
                this.params.put(property+"_orIn",String.join(",",val));
            }
            return this;
        }
        public Criteria<T> orIn(Fn<T,Object> fn, boolean b, List<String> val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_orIn",String.join(",",val));
            }
            return this;
        }
        public Criteria<T> orNotIn(String property, List<String> val){
            this.params.put(property+"_orNotIn",String.join(",",val));
            return this;
        }
        public Criteria<T> orNotIn(Fn<T,Object> fn, List<String> val){
            this.params.put(this.fnToFieldName(fn)+"_orNotIn",String.join(",",val));
            return this;
        }
        public Criteria<T> orNotIn(String property, boolean b, List<String> val){
            if (b){
                this.params.put(property+"_orNotIn",String.join(",",val));
            }
            return this;
        }
        public Criteria<T> orNotIn(Fn<T,Object> fn, boolean b, List<String> val){
            if (b){
                this.params.put(this.fnToFieldName(fn)+"_orNotIn",String.join(",",val));
            }
            return this;
        }
        public Criteria<T> and(Criteria<T> criteria){
            this.isAnd = true;
            this.criteriaAndList.add(criteria);
            return criteria;
        }
        public Criteria<T> or(Criteria<T> criteria){
            this.isOr = true;
            this.criteriaOrList.add(criteria);
            return criteria;
        }
        public Criteria<T> orderBy(String orderBy) {
            this.orderBy = orderBy;
            return this;
        }
        public Criteria<T> orderBy(Fn<T,Object> fn) {
            this.orderBy = fnToFieldName(fn);
            return this;
        }
        public Criteria<T> asc() {
            this.order = "asc";
            return this;
        }
        public Criteria<T> desc() {
            this.order = "desc";
            return this;
        }

        public Class<T> getClazz() {
            return clazz;
        }
        public Map<String, Object> getParams() {
            return params;
        }
        public String getOrderBy() {
            return orderBy;
        }
        public String order() {
            return this.order;
        }
        public boolean isAnd() {
            return isAnd;
        }
        public boolean isOr() {
            return isOr;
        }
        public List<Criteria<T>> getCriteriaAndList() {
            return criteriaAndList;
        }
        public List<Criteria<T>> getCriteriaOrList() {
            return criteriaOrList;
        }

        private final Pattern GET_PATTERN = Pattern.compile("^get[A-Z].*");
        private final Pattern IS_PATTERN = Pattern.compile("^is[A-Z].*");
        private String fnToFieldName(Fn<T,Object> fn){
            try {
                Method method = fn.getClass().getDeclaredMethod("writeReplace");
                method.setAccessible(true);
                SerializedLambda serializedLambda = ((SerializedLambda) method.invoke(fn));
                String name = serializedLambda.getImplMethodName();
                if (GET_PATTERN.matcher(name).matches()){
                    name = name.substring(3);
                }else if (IS_PATTERN.matcher(name).matches()){
                    name = name.substring(2);
                }
                return Introspector.decapitalize(name);
            }catch (ReflectiveOperationException e){
                throw new RuntimeException(e);
            }
        }

        public interface Fn<T,R> extends Function<T,R>, Serializable{}
    }
}